package androidgame.framework.impl;

import java.util.ArrayList;
import java.util.List;

import android.util.FloatMath;
import android.view.MotionEvent;
import androidgame.framework.Input.Gesture;
import androidgame.framework.Input.TouchEvent;
import androidgame.framework.Pool.PoolObjectFactory;
import androidgame.framework.Pool;
// Currently supports 3 gestures, drag, zoom, and fling. Identifiers are organized by which variables are relevant for each gesture.
public class GestureHandler {
	Pool<Gesture> gesturePool;
	List<Gesture> gestures = new ArrayList<Gesture>();
	List<Gesture> gesturesBuffer = new ArrayList<Gesture>();
	float scaleX;
	float scaleY;
	
	public static final int NONE = 0;

	public static final int DRAG = 1;
	int mode;
	int previousX;
	int previousY;
	
	public static final int ZOOM = 2;
	float previousDistance;
	
	public static final int FLING = 3;
	TouchEvent startEvent;
	TouchEvent endEvent;
	float velocityX;
	float velocityY;
	float startTime;
	float deltaTime;
	float moveEndTime;

	TouchHandler handler;

	public GestureHandler(TouchHandler handler, float scaleX, float scaleY) {
		PoolObjectFactory<Gesture> factory = new PoolObjectFactory<Gesture>() {
			@Override
			public Gesture createObject() {
				return new Gesture();
			}
		};
		gesturePool = new Pool<Gesture>(factory, 100);

		this.scaleX = scaleX;
		this.scaleY = scaleY;
		this.handler = handler;
		startEvent = new TouchEvent();
		startEvent.type = TouchEvent.TOUCH_UP;

	}

	public boolean onTouch(MotionEvent event) {
		synchronized (this) {
			int action = event.getAction() & MotionEvent.ACTION_MASK;
			int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
			int pointerId = event.getPointerId(pointerIndex);
			Gesture gestureEvent;

			switch (action) {
			case MotionEvent.ACTION_DOWN:
			case MotionEvent.ACTION_POINTER_DOWN:
				if (pointerId == 0) {
					mode = DRAG;
					startEvent.x = previousX = (int) (event.getX() * scaleX);
					startEvent.y = previousY = (int) (event.getY() * scaleY);
					startEvent.pointer = pointerId;
					startTime = System.nanoTime();
				} else if (pointerId == 1) {
					previousDistance = touchSpacing(event);
					if (previousDistance > 10f)
						mode = ZOOM;
				}
				break;

			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_POINTER_UP:
			case MotionEvent.ACTION_CANCEL:
				if (mode == DRAG) {
					if ((System.nanoTime() - moveEndTime) / 1000000000.0f > .075f){
						mode = NONE;
						break;
					}
					deltaTime = (System.nanoTime() - startTime) / 1000000000.0f;
					gestureEvent = gesturePool.newObject();
					gestureEvent.type = Gesture.GESTURE_FLING;
					gestureEvent.velocityX = (startEvent.x - event.getX()) / deltaTime;
					gestureEvent.velocityY = (startEvent.y - event.getY()) / deltaTime;
					gestureEvent.event1 = handler.touchEventPool.newObject();
					gestureEvent.event1.x = startEvent.x;
					gestureEvent.event1.y = startEvent.y;
					gestureEvent.event1.pointer = startEvent.pointer;
					gestureEvent.event2 = handler.touchEventPool.newObject();
					gestureEvent.event2.x = (int) (event.getX() * scaleX);
					gestureEvent.event2.y = (int) (event.getY() * scaleY);
					gestureEvent.event2.pointer = pointerId;
					gesturesBuffer.add(gestureEvent);
					}
				mode = NONE;
				break;

			case MotionEvent.ACTION_MOVE:
				if (mode == DRAG) {
					gestureEvent = gesturePool.newObject();
					gestureEvent.type = Gesture.GESTURE_DRAG;
					gestureEvent.dragDistanceX = previousX - (int) (event.getX() * scaleX);
					gestureEvent.dragDistanceY = previousY - (int) (event.getY() * scaleY);
					gesturesBuffer.add(gestureEvent);
					previousX = (int) (event.getX() * scaleX);
					previousY = (int) (event.getY() * scaleY);
					moveEndTime = System.nanoTime();
				} else if (mode == ZOOM) {
					float newDistance;
					newDistance = touchSpacing(event);
					if (newDistance > 10f) {
						gestureEvent = gesturePool.newObject();
						gestureEvent.type = Gesture.GESTURE_ZOOM;
						gestureEvent.zoom = previousDistance / newDistance;
						gesturesBuffer.add(gestureEvent);
						previousDistance = newDistance;
					}
				}
				break;

			}

			return true;
		}
	}

	public float touchSpacing(MotionEvent event) {
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return FloatMath.sqrt(x * x + y * y);

	}

	public List<Gesture> getGestures() {
		synchronized (this) {
			int len = gestures.size();
			for (int i = 0; i < len; i++) {
				Gesture gesture = gestures.get(i);
				if (gesture.type == Gesture.GESTURE_FLING){
					handler.touchEventPool.free(gesture.event1);
					handler.touchEventPool.free(gesture.event2);
				}
				gesturePool.free(gestures.get(i));
			}
			gestures.clear();
			gestures.addAll(gesturesBuffer);
			gesturesBuffer.clear();
			return gestures;
		}
	}

}
